// websocket class
import dayjs from 'dayjs'

export default class {
  /**
   * @param connectionUrl 连接的url
   * @param opts 其它配置项 | Other configuration items
   */
  constructor(connectionUrl, opts = {}) {
    // 添加初始化
    this.reconnectionCount = 0
    // 如果url以//开始对其进行处理添加正确的websocket协议前缀 | If the URL starts with // to process it, add the correct websocket protocol prefix
    if (connectionUrl.startsWith('//')) {
      // 当前网站如果为https请求则添加wss前缀否则添加ws前缀 | If the current website is an https request, add the wss prefix, otherwise add the ws prefix
      const scheme = window.location.protocol === 'https:' ? 'wss' : 'ws'
      connectionUrl = `${scheme}:${connectionUrl}`
    }
    // 将处理好的url和opts赋值给当前类内部变量 | Assign the processed url and opts to the internal variables of the current class
    this.connectionUrl = connectionUrl
    this.opts = opts
    this.reconnection = this.opts.reconnection || true
    this.reconnectionAttempts = this.opts.reconnectionAttempts || Infinity
    this.reconnectionDelay = this.opts.reconnectionDelay || 1000
    this.heartDelay = this.opts.heartDelay || 60 * 1000

    // 建立连接 | establish connection
    this.connect(connectionUrl, opts)

    // 事件触发
    this.onEvent()
  }

  // 连接websocket | Connect websocket
  connect(connectionUrl, opts) {
    // 获取配置参数传入的协议
    const protocol = opts.protocol || ''
    // 如果没传协议就建立一个正常的websocket连接否则就创建带协议的websocket连接
    this.WebSocket = opts.WebSocket || (protocol === '' ? new WebSocket(connectionUrl) : new WebSocket(connectionUrl, protocol))
    // 增加json发送 如果websocket中没有senObj就添加这个方法对象
    if (!('sendObj' in this.WebSocket)) {
      // 将发送的消息转为json字符串
      this.WebSocket.sendObj = (obj) => {
        if (!obj.messageId) {
          obj.messageId = this.getUUID()
        }
        if (!obj.sendTime) {
          obj.sendTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
        }
        this.WebSocket.send(JSON.stringify(obj))
      }
    }
    return this.WebSocket
  }

  // 发送心跳
  sendHeart() {
    this.WebSocket.sendObj({
      command: 3,
      heartBeat: {
        msg: 'ping',
      },
    })
  }

  // 延迟发送心跳
  sendHeartDelay() {
    setTimeout(() => {
      this.sendHeart()
    }, this.heartDelay)
  }

  // 重新连接 | reconnect
  reconnect() {
    console.error('websocket===> reconnect:', this.reconnectionCount)
    // 已重连次数小于等于设置的连接次数时执行重连
    if (this.reconnectionCount <= this.reconnectionAttempts) {
      this.reconnectionCount++
      // 清理上一次重连时的定时器
      clearTimeout(this.reconnectTimeoutId)
      // 开始重连
      this.reconnectTimeoutId = setTimeout(() => {
        // 重新连接
        this.connect(this.connectionUrl, this.opts)
        // 触发WebSocket事件
        this.onEvent()
      }, this.reconnectionDelay)
    }
  }

  // 事件分发
  onEvent() {
    this.WebSocket.onopen = () => {
      this.reconnectionCount = 0
      if (this.sendLogin) {
        this.sendLogin()
      }
    }

    this.WebSocket.onclose = () => {
      // 如果处于重连状态且事件为onclose时调用重连方法
      if (this.reconnection) {
        this.reconnect()
      }
    }

    this.WebSocket.onmessage = (event) => {
      // 处理返回的消息
      this.onMessage(event)
    }

    this.WebSocket.onerror = (event) => {
      // 打印错误的事件
      console.error('websocket===> error:', event)
    }
  }

  // 分发消息
  distributeMessage(event) {
    if (this.onListener) {
      this.onListener(event)
    }
  }

  // 处理返回的消息
  onMessage(event) {
    const data = JSON.parse(event.data)
    switch (data.command) {
      // 确定建立连接
      case 2:
        this.sendHeart()
        break
      // 心跳
      case 4:
        this.sendHeartDelay()
        break
      // 20:通知消息V2, 21:收到消息反馈V2, 22:撤回消息V2
      case 20:
      case 22:
        this.distributeMessage(data) // 分发
        this.replyMessage(data)
        break
      default:
        this.replyMessage(data)
        break
    }
  }

  // 默认返回
  replyMessage(data) {
    let id
    // 20、22 有ID
    if (data.command == 20 || data.command == 22) {
      id = data.noticeV2.id
    } else {
      id = this.getUUID()
    }

    this.WebSocket.sendObj({
      command: 21,
      noticeV2: {
        id,
      },
    })
  }

  // 获取uuid
  getUUID() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
      const r = (Math.random() * 16) | 0,
        v = c == 'x' ? r : (r & 0x3) | 0x8
      return v.toString(16)
    })
  }
}
